package lumis.content.core;

import java.io.Serializable;
import java.util.*;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EntityListeners;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToOne;
import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
import javax.persistence.Table;

import lumis.content.ContentManagerFactory;
import lumis.portal.PortalException;
import lumis.portal.event.persistence.PortalEventEntityListener;
import lumis.portal.manager.ManagerFactory;
import lumis.portal.stability.StableMinor;
import lumis.portal.transaction.AbstractTransactionObserver;
import lumis.portal.transaction.PortalTransactionFactory;

import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.CollectionId;
import org.hibernate.annotations.CollectionOfElements;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.Type;

/**
 * A content publication definition entity class.
 *
 * @stereotype entity
 * 
 * @version $Revision: 13082 $ $Date: 2011-05-27 16:03:12 -0300 (Fri, 27 May 2011) $
 * @since 4.2.0
 */
@StableMinor(version = "6.0", sinceVersion = "4.2")
@Entity
@Table(name="lum_ContentPublication")
@GenericGenerator(name="portal-guid", strategy="lumis.portal.dao.hibernate.GuidGenerator")
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
@EntityListeners(value=PortalEventEntityListener.class)
@SuppressWarnings("serial")
public class ContentPublication implements Cloneable, Serializable
{
	@Id @GeneratedValue(generator="portal-guid")
	private String id;
	
	private boolean highlight;
	
	private Date highlightEndDateTime;
	
	private Date publishEndDateTime;
	
	private Date publishStartDateTime;
	
	private boolean published;
	
	private boolean waitingForExpiration;
	
	private boolean waitingForPublishing;
	
	@CollectionOfElements
	@JoinTable(name="lum_ContentPubServiceInst", joinColumns=@JoinColumn(name="contentPublicationId"))
	@Column(name="serviceInstanceId", nullable=false)
	@CollectionId(columns=@Column(name="id"),type=@Type(type="string"),generator="portal-guid")
	private Collection<String> serviceInstanceIds = new ArrayList<String>();
	
	@CollectionOfElements
	@JoinTable(name="lum_ContentPubPrincipal", joinColumns=@JoinColumn(name="contentPublicationId"))
	@Column(name="principalId", nullable=false)
	@CollectionId(columns=@Column(name="id"),type=@Type(type="string"),generator="portal-guid")
	private Collection<String> principalIds = new ArrayList<String>();
	
	@ManyToOne(fetch=FetchType.LAZY)
	@JoinColumn(name="contentVersionId")
	private ContentVersion contentVersion;
	
	// -- Property Methods -- //
	
	/**
	 * Return the unique identifier for this publication object.
	 * @return the unique identifier for this publication object.
	 * @since 4.2.0
	 */
	public String getId()
	{
		return id;
	}
	
	/**
	 * Indicates if this publication is currently highlighted.
	 * @return true if it is currently highlighted, false otherwise.
	 * @since 4.2.0
	 */
	public boolean isHighlight()
	{
		return highlight;
	}
	
	/**
	 * Sets the highlight property for this publication.
	 * @param highlight true to enable the highlight, false to disable it.
	 * @see #getHighlightEndDateTime()
	 * @since 4.2.0
	 */
	public void setHighlight(boolean highlight)
	{
		this.highlight = highlight;
	}
	
	/**
	 * Returns the date-time when this publication should no longer be highlighted.
	 * @return the date-time, or <code>null</code> if no maximum limit for 
	 * highlight exists.
	 * @since 4.2.0
	 */
	public Date getHighlightEndDateTime()
	{
		return highlightEndDateTime;
	}
	
	/**
	 * Sets the date-time when this publication should no longer be highlighted.
	 * If set to <code>null</code>, there will be no maximum time limit for the
	 * highlight.
	 * @param highlightEnd the date-time value to set.
	 * @since 4.2.0
	 */
	public void setHighlightEndDateTime(Date highlightEndDateTime)
	{
		this.highlightEndDateTime = highlightEndDateTime;
	}
	
	/**
	 * Returns the maximum date-time limit for this publication.
	 * @return the date-time, or <code>null</code> if no maximum limit for 
	 * this publication exists.
	 * @since 4.2.0
	 */
	public Date getPublishEndDateTime()
	{
		return publishEndDateTime;
	}
	
	/**
	 * Sets the maximum time limit for this publication.
	 * If set to <code>null</code>, there will be no maximum time limit for the
	 * publication.
	 * @param publishEndDateTime the date-time value to set.
	 * @since 4.2.0
	 */
	public void setPublishEndDateTime(Date publishEndDateTime)
	{
		this.publishEndDateTime = publishEndDateTime;
	}
	
	/**
	 * Returns the initial date-time from which the publication is to be done.
	 * @return the date-time, or <code>null</code> if no minimum limit for 
	 * this publication exists.
	 * @since 4.2.0
	 */
	public Date getPublishStartDateTime()
	{
		return publishStartDateTime;
	}
	
	/**
	 * Sets the initial date-time from which the publication is to be done.
	 * If set to <code>null</code>, there will be no minimum time limit for the
	 * publication.
	 * @param publishStartDateTime the date-time value to set.
	 * @since 4.2.0
	 */
	public void setPublishStartDateTime(Date publishStartDateTime)
	{
		this.publishStartDateTime = publishStartDateTime;
	}
	
	/**
	 * Indicates if this publication is currently published. The value for
	 * this property is automatically set by the content framework accordingly
	 * to other content properties.
	 * @return true if it is currently published, false otherwise.
	 * @since 4.2.0
	 */
	public boolean isPublished()
	{
		return published;
	}
	void setPublished(boolean published)
	{
		this.published = published;
	}
	boolean isWaitingForExpiration()
	{
		return waitingForExpiration;
	}
	void setWaitingForExpiration(boolean waitingForExpiration)
	{
		this.waitingForExpiration = waitingForExpiration;
	}
	boolean isWaitingForPublishing()
	{
		return waitingForPublishing;
	}
	void setWaitingForPublishing(boolean waitingForPublishing)
	{
		this.waitingForPublishing = waitingForPublishing;
	}
	
	/**
	 * Returns the identifiers of the service instances this publication
	 * is done to. The returned collection may be changed to edit the
	 * service instances associated with this publication.
	 * @return collection containing the associated service instance identifiers.
	 * @since 4.2.0
	 */
	public Collection<String> getServiceInstanceIds()
	{
		return serviceInstanceIds;
	}
	
	/**
	 * Returns the identifiers of the principals this publication
	 * is done to. The returned collection may be changed to edit the
	 * principals associated with this publication.
	 * @return collection containing the associated principal identifiers.
	 * @since 4.2.0
	 */
	public Collection<String> getPrincipalIds()
	{
		return principalIds;
	}
	
	/**
	 * Returns the content version this publication belongs to.
	 * @return the content version.
	 * @since 4.2.0
	 */
	public ContentVersion getContentVersion()
	{
		return contentVersion;
	}
	void setContentVersion(ContentVersion contentVersion)
	{
		this.contentVersion = contentVersion;
	}
	
	// -- Operations -- //
	
	/**
	 * Deletes this publication.
	 * @since 4.2.0
	 */
	public void delete() throws PortalException
	{
		getContentVersion().removeContentPublication(this);
		ManagerFactory.getEntityManager().remove(this);
	}
	
	/**
	 * Updates this publication's calculated fields. This calculation occurs
	 * automatically before adding or updating the publication in the 
	 * persistence.
	 * <p>
	 * This method may be called directly to force immediate fields calculations,
	 * or when no other change is done but it is desired to update the 
	 * calculated fields.
	 * 
	 * @since 4.2.0
	 */
	public void updateCalculatedFields() throws PortalException
	{
		Date now = new Date();
		
		// update published value. If inside publication period
		if (((publishEndDateTime == null || !publishEndDateTime.before(now)) &&
				(publishStartDateTime == null || publishStartDateTime.before(now))))
		{
			setPublished(true);
		}
		else
		{
			setPublished(false);
		}
		
		// disable highlight, if it is enabled but highlightEndDateTime has passed
		if (isHighlight() && getHighlightEndDateTime() != null && getHighlightEndDateTime().before(now))
			setHighlight(false);
		
		// update waiting for fields
		if (isPublished())
		{
			setWaitingForPublishing(false);
			
			if (publishEndDateTime == null)
				setWaitingForExpiration(false);
			else
				setWaitingForExpiration(true);				
		}
		else
		{
			setWaitingForExpiration(false);
			
			if (publishStartDateTime == null || publishStartDateTime.before(now))
				setWaitingForPublishing(false);
			else
				setWaitingForPublishing(true);				
		}
	}
	
	@Override
	protected ContentPublication clone()
	{
		try
		{
			ContentPublication publication = (ContentPublication)super.clone();
			publication.id = null;
			publication.contentVersion = null;
			publication.serviceInstanceIds = new HashSet<String>(publication.serviceInstanceIds);
			publication.principalIds = new HashSet<String>(publication.principalIds);
			return publication;
		}
		catch (CloneNotSupportedException e)
		{
			throw new RuntimeException(e);
		}
	}
	
	// -- Events -- //
	
	@PrePersist
	@PreUpdate
	void doPrePersistAndUpdate() throws PortalException
	{
		updateCalculatedFields();
		
		// TODO: an observer in workflow package should catch a generic 
		// publication persist/update event and execute the code below

		if (getContentVersion() == getContentVersion().getContentLocale().getActiveVersion())
		{
			// before commit, auto-archive content if necessary
			PortalTransactionFactory.getCurrentTransaction().addObserver(new AbstractTransactionObserver()
			{
				@Override
				public boolean beforeCommit() throws PortalException
				{
					if (!isPublished())
					{
						ContentManagerFactory.getWorkflowManager().autoArchivePublishedContent(
								getContentVersion().getContentLocale(), 
								PortalTransactionFactory.getCurrentTransaction());
					}
					
					return true;
				}
			});
		}
	}
}